<template>
  <div v-loading="loading" class="captcha-content">
    <!--文字点击验证码-->
    <div v-if="data.captcha.type === 'WORD_IMAGE_CLICK'" class="text-click-captcha">
      <div class="captcha-top">
        <div class="captcha-top-title">{{ title }}</div>
        <div class="captcha-top-template-image">
          <el-image
            :src="data.captcha.templateImage"
            style="width: 100px;margin-left: 30px;transform: translateY(5px);"
            fit="contain"
          />
        </div>
        <div class="captcha-top-operate">
          <el-button v-throttle class="el-icon-refresh" circle @click="getCode" />
          <el-button class="el-icon-close" circle @click="close" />
        </div>
      </div>
      <div id="click-image-div" class="captcha-image" :style="{ height: bgImageHeight + 'px' }">
        <el-image
          id="captcha-image"
          style="cursor: pointer;"
          :src="data.captcha.backgroundImage"
          fit="contain"
          @load="computeBgRatio"
          @click="imageClick"
        />
        <transition name="el-fade-in-linear">
          <div v-if="validateCodeStatus === 1" class="validate-code-success" style="bottom: 0;">
            验证成功，耗时{{ (validateData.endSlidingTime.getTime() - validateData.startSlidingTime.getTime()) / 1000 }}秒
          </div>
          <div v-if="validateCodeStatus === 2" class="validate-code-fail" style="bottom: 0;">验证失败，请稍后重试</div>
        </transition>
      </div>
    </div>
    <!--滑块验证码-->
    <div v-if="data.captcha.type === 'SLIDER'" class="slider-move-captcha">
      <div class="captcha-top">
        <div class="captcha-top-title">{{ title }}</div>
        <div class="captcha-top-operate">
          <el-button v-throttle class="el-icon-refresh" circle @click="getCode" />
          <el-button class="el-icon-close" circle @click="close" />
        </div>
      </div>
      <div class="captcha-image">
        <el-image
          id="captcha-image"
          :src="data.captcha.backgroundImage"
          fit="contain"
          @load="computeBgRatio"
        />
        <el-image
          id="slider-block-image"
          class="slider-block-image"
          :style="{
            top: sliderBlockTop + 'px',
            left: sliderBlockLeft + 'px',
            width: sliderBlockWidth + 'px',
            height: sliderBlockHeight + 'px',
          }"
          :src="data.captcha.templateImage"
          fit="contain"
        />
        <transition name="el-fade-in-linear">
          <div v-if="validateCodeStatus === 1" class="validate-code-success">
            验证成功，耗时{{ (validateData.endSlidingTime.getTime() - validateData.startSlidingTime.getTime()) / 1000 }}秒
          </div>
          <div v-if="validateCodeStatus === 2" class="validate-code-fail">验证失败，请稍后重试</div>
        </transition>
      </div>
      <div class="slider-move-track">
        <span class="slider-move-track-tips">向右拖动滑块完成拼图</span>
        <div
          :style="{
            left: sliderBlockLeft + 'px',
            width: sliderBlockWidth + 'px',
          }"
          class="slider-bottom-move-block"
          @mousedown="sliderBlockDown"
          @touchstart="sliderBlockDown"
        >
          <svg-icon icon-class="right-arrow.svg" style="height: 18px;" />
        </div>
      </div>
    </div>
    <!--旋转验证码-->
    <div v-if="data.captcha.type === 'ROTATE'" class="slider-rotate-captcha">
      <div class="captcha-top">
        <div class="captcha-top-title">{{ title }}</div>
        <div class="captcha-top-operate">
          <el-button v-throttle class="el-icon-refresh" circle @click="getCode" />
          <el-button class="el-icon-close" circle @click="close" />
        </div>
      </div>
      <div class="captcha-image">
        <el-image
          id="captcha-image"
          :src="data.captcha.backgroundImage"
          fit="contain"
          @load="computeBgRatio"
        />
        <el-image
          id="slider-block-image"
          class="slider-block-image"
          :style="{
            top: sliderBlockTop + 'px',
            left: rotateBlockLeft + 'px',
            width: sliderBlockWidth + 'px',
            height: sliderBlockHeight + 'px',
            transform: 'rotate(' + rotateAngle + 'deg)',
          }"
          :src="data.captcha.templateImage"
          fit="contain"
        />
        <transition name="el-fade-in-linear">
          <div v-if="validateCodeStatus === 1" class="validate-code-success">
            验证成功，耗时{{ (validateData.endSlidingTime.getTime() - validateData.startSlidingTime.getTime()) / 1000 }}秒
          </div>
          <div v-if="validateCodeStatus === 2" class="validate-code-fail">验证失败，请稍后重试</div>
        </transition>
      </div>
      <div class="slider-move-track">
        <span class="slider-move-track-tips">向右拖动滑块完成拼图</span>
        <div
          :style="{
            left: sliderBlockLeft + 'px',
            width: defaultBlockWidth + 'px',
          }"
          class="slider-bottom-move-block"
          @mousedown="sliderBlockDown"
          @touchstart="sliderBlockDown"
        >
          <svg-icon icon-class="right-arrow.svg" style="height: 18px;" />
        </div>
      </div>
    </div>
    <!--滑动还原验证码-->
    <div v-if="data.captcha.type === 'CONCAT'" class="slider-concat-captcha">
      <div class="captcha-top">
        <div class="captcha-top-title">{{ title }}</div>
        <div class="captcha-top-operate">
          <el-button v-throttle class="el-icon-refresh" circle @click="getCode" />
          <el-button class="el-icon-close" circle @click="close" />
        </div>
      </div>
      <div class="captcha-image">
        <div
          id="captcha-image"
          class="concat-bg-image"
          :style="{
            height: bgImageHeight + 'px',
            backgroundImage: data.captcha.backgroundImage && 'url(' + data.captcha.backgroundImage + ')'
          }"
        >
          <div
            style="position: absolute;width:100%;"
            :style="{
              height: concatBlockHeight + 'px',
              backgroundSize: '100% ' + bgImageHeight + 'px',
              backgroundPosition: sliderBlockLeft + 'px 0px',
              backgroundImage: data.captcha.backgroundImage && 'url(' + data.captcha.backgroundImage + ')'
            }"
          />
          <transition name="el-fade-in-linear">
            <div v-if="validateCodeStatus === 1" class="validate-code-success" style="bottom: 0;">
              验证成功，耗时{{ (validateData.endSlidingTime.getTime() - validateData.startSlidingTime.getTime()) / 1000 }}秒
            </div>
            <div v-if="validateCodeStatus === 2" class="validate-code-fail" style="bottom: 0;">验证失败，请稍后重试</div>
          </transition>
        </div>
      </div>
      <div class="slider-move-track" style="margin-top: 10px;">
        <span class="slider-move-track-tips">向右拖动滑块完成拼图</span>
        <div
          :style="{
            left: sliderBlockLeft + 'px',
            width: defaultBlockWidth + 'px',
          }"
          class="slider-bottom-move-block"
          @mousedown="sliderBlockDown"
          @touchstart="sliderBlockDown"
        >
          <svg-icon icon-class="right-arrow.svg" style="height: 18px;" />
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { getCode, validateCode } from '@/api/api'
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)
export default {
  data() {
    return {
      loading: false, // 加载
      validateCodeStatus: 0, // 0：未校验 1：成功 2：失败
      ratio: null, // 图片缩放比例
      id: '', //  验证码唯一ID
      title: '', // 提示标题
      type: '', // 验证码类型  CLICK/SLIDER
      titleMap: {
        WORD_IMAGE_CLICK: '请依次点击:',
        SLIDER: '拖动滑块完成拼图',
        CONCAT: '拖动滑块完成拼图',
        ROTATE: '拖动滑块完成拼图'
      },
      typeMap: {
        WORD_IMAGE_CLICK: 'CLICK',
        SLIDER: 'SLIDER',
        CONCAT: 'SLIDER',
        ROTATE: 'SLIDER'
      },
      data: { // 验证码返回参数
        captcha: {
          type: 'CONCAT'
        }
      },
      captchaImage: null, // 背景图
      bgImageWidth: 0, // 背景图宽度
      bgImageHeight: 0, // 背景图高度
      startX: 0, // 开始滑动的轴
      moveDown: false, // 是否处于拖动中
      validateData: {}, // 验证参数
      clickMarks: [], //  点击的dom数组
      movableWidth: 0, // 滑动块可滑动宽度
      sliderBlockTop: 0, // 拼图top值
      sliderBlockLeft: 0, // 拼图left值
      sliderBlockWidth: 0, // 拼图宽度
      sliderBlockHeight: 0, // 拼图高度
      rotateBlockLeft: 0, // 旋转图left值
      defaultBlockWidth: 50, // 旋转图下方滑动块宽度
      rotateAngle: 0, // 旋转角度
      concatBlockHeight: 0 // concat滑块高度
    }
  },
  // 销毁事件
  beforeDestroy() {
    window.removeEventListener('mousemove', this.sliderBlockMove)
    window.removeEventListener('mouseup', this.sliderBlockUp)
    window.removeEventListener('touchmove', this.sliderBlockMove)
    window.removeEventListener('touchend', this.sliderBlockUp)
  },
  methods: {
    // 获取验证码
    getCode() {
      this.loading = true
      this.validateCodeStatus = 0
      if (this.data.captcha.type === 'WORD_IMAGE_CLICK') this.clearClickMarks()
      getCode().then(res => {
        this.data = res.data
        this.type = this.typeMap[res.data.captcha.type]
        this.title = this.titleMap[res.data.captcha.type]
        this.id = res.data.id
        this.validateData = {
          sliderImageWidth: res.data.captcha.templateImageWidth,
          sliderImageHeight: res.data.captcha.templateImageHeight,
          bgImageHeight: res.data.captcha.backgroundImageHeight,
          bgImageWidth: res.data.captcha.backgroundImageWidth,
          trackList: [],
          data: res.data.captcha.data
        }
        if (this.data.captcha.type === 'CONCAT') {
          this.$nextTick(() => {
            this.computeBgRatio()
          })
        }
        this.sliderBlockLeft = 0
        this.rotateAngle = 0
        this.rotateAddCount = 0
      }).finally(() => {
        this.loading = false
      })
    },
    // 校验验证码
    validateCode() {
      this.loading = true
      validateCode(this.id, this.validateData).then(res => {
        if (res.message === 'true') {
          this.validateCodeStatus = 1
          setTimeout(() => {
            this.$emit('ok', this.id)
          }, 1500)
        } else {
          this.validateCodeStatus = 2
          setTimeout(() => {
            this.getCode()
          }, 1500)
        }
      }).catch(e => {
        this.getCode()
      }).finally(() => {
        this.loading = false
      })
    },
    // 点击图片
    imageClick(e) {
      if (this.validateData.trackList.length === 0) {
        this.validateData.startSlidingTime = new Date()
      }
      this.trackPush(e.offsetX, e.offsetY)
      if (this.validateData.trackList.length <= 4) {
        this.showClickNumber(this.validateData.trackList.length, e)
      }
      if (this.validateData.trackList.length === 4) {
        this.validateData.endSlidingTime = new Date()
        this.validateCode()
      }
    },
    // 点位push
    trackPush(x, y) {
      if (this.data.captcha.type === 'ROTATE') {
        x = this.rotateAngle * (this.ratio * this.bgImageWidth / 360)
      } else {
        x = x * this.ratio
      }
      this.validateData.trackList.push({
        x: x,
        y: y * this.ratio,
        t: new Date().getTime() - this.validateData.startSlidingTime.getTime(),
        type: this.type
      })
    },
    // 计算背景图缩放比例
    computeBgRatio() {
      this.captchaImage = document.getElementById('captcha-image')
      if (this.data.captcha.type === 'CONCAT') {
        this.bgImageWidth = this.captchaImage.clientWidth
      } else {
        this.bgImageWidth = this.captchaImage.width
      }
      this.bgImageHeight = this.bgImageWidth / this.data.captcha.backgroundImageWidth * this.data.captcha.backgroundImageHeight
      this.ratio = this.data.captcha.backgroundImageWidth / this.bgImageWidth
      this.sliderBlockWidth = this.data.captcha.templateImageWidth / this.ratio
      this.sliderBlockHeight = this.data.captcha.templateImageHeight / this.ratio
      this.rotateBlockLeft = (this.bgImageWidth - this.sliderBlockWidth) / 2
      if (this.data.captcha.data && this.data.captcha.data.randomY) {
        this.concatBlockHeight = (this.data.captcha.backgroundImageHeight - this.data.captcha.data.randomY) / this.ratio
      }
      this.$emit('refresh')
    },
    // 显示点击数字
    showClickNumber(number, e) {
      const clickImageDiv = document.getElementById('click-image-div')
      const div = document.createElement('div')
      div.innerHTML = number
      div.style.left = e.offsetX - 15 + 'px'
      div.style.top = e.offsetY - 12 + 'px'
      div.classList.add('captcha-click-mark')
      this.clickMarks.push(div)
      clickImageDiv.appendChild(div)
    },
    // 清空点击的dom
    clearClickMarks() {
      const clickImageDiv = document.getElementById('click-image-div')
      if (this.clickMarks.length > 0) {
        this.clickMarks.forEach(item => {
          clickImageDiv.removeChild(item)
        })
        this.clickMarks = []
      }
    },
    // 关闭验证码组件
    close() {
      this.$emit('close')
    },
    // 开始拖动
    sliderBlockDown(e) {
      this.moveDown = true
      this.startX = isMobile ? e.changedTouches[0].pageX : e.pageX
      this.validateData.startSlidingTime = new Date()
      this.trackPush(this.sliderBlockLeft, 0)
      window.addEventListener('mousemove', this.sliderBlockMove)
      window.addEventListener('mouseup', this.sliderBlockUp)
      window.addEventListener('touchmove', this.sliderBlockMove)
      window.addEventListener('touchend', this.sliderBlockUp)
    },
    // 拖动结束
    sliderBlockUp(e) {
      this.moveDown = false
      window.removeEventListener('mousemove', this.sliderBlockMove)
      window.removeEventListener('mouseup', this.sliderBlockUp)
      window.removeEventListener('touchmove', this.sliderBlockMove)
      window.removeEventListener('touchend', this.sliderBlockUp)
      this.validateData.endSlidingTime = new Date()
      this.trackPush(this.sliderBlockLeft, 0)
      this.validateCode()
    },
    // 拖动中
    sliderBlockMove(e) {
      if (this.moveDown) {
        const moveX = (isMobile ? e.changedTouches[0].pageX : e.pageX) - this.startX
        this.movableWidth = this.bgImageWidth - this.sliderBlockWidth
        if (this.data.captcha.type === 'ROTATE' || this.data.captcha.type === 'CONCAT') {
          this.movableWidth = this.bgImageWidth - this.defaultBlockWidth
          if (moveX >= 0 && moveX <= this.movableWidth) {
            this.rotateAngle = moveX / this.movableWidth * 360
          }
        }
        if (moveX <= 0) {
          this.sliderBlockLeft = 0
        } else {
          this.sliderBlockLeft = moveX > this.movableWidth ? this.movableWidth : moveX
        }
        if (this.validateData.trackList.length <= (this.data.captcha.backgroundImageWidth * 5 - 5)) {
          this.trackPush(this.sliderBlockLeft, 0)
        }
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.captcha-content {
  min-height: 240px;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  .captcha-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 22px;
    .captcha-top-operate {
      ::v-deep .el-button {
        border: none;
        padding: 0;
        font-size: 25px;
      }
    }
  }
  .captcha-image {
    position: relative;
    overflow: hidden;
    .slider-block-image {
      position: absolute;
      left: 0;
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
    }
    .concat-bg-image {
      position: relative;
      width: 100%;
      height: 100%;
      background-repeat: no-repeat;
      background-size: 100% 100%;
    }
  }
  .slider-move-track {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 30px;
    background-color: #c0c4cc;
    border-radius: 2px;
    .slider-move-track-tips {
      margin-left: 40px;
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
    }
    .slider-bottom-move-block {
      display: flex;
      align-items: center;
      justify-content: center;
      position: absolute;
      left: 0;
      height: 30px;
      cursor: move;
      border-radius: 2px;
      background-color: #ffffff;
      box-shadow: 0 0 5px 1px #999999;
    }
  }
  .validate-code-success {
    text-align: center;
    line-height: 30px;
    position: absolute;
    width: 100%;
    height: 30px;
    bottom: 5px;
    color: #ffffff;
    background-color: #3eb600;
  }
  .validate-code-fail {
    text-align: center;
    line-height: 30px;
    position: absolute;
    width: 100%;
    height: 30px;
    bottom: 5px;
    color: #ffffff;
    background-color: #ff0000;
  }
}
</style>
<style>
.captcha-click-mark {
  width: 25px;
  height: 25px;
  color: #fff;
  background: #20a0ff;
  border: 2px solid #fff;
  border-radius: 50%;
  position: absolute;
  cursor: pointer;
  z-index: 999;
  display: flex;
  align-items: center;
  justify-content: center;
}
</style>
